package IC.lir;


import java.util.*;
import java.util.Map.Entry;

import IC.lir.operands.Label;

import util.Pair;

import IC.AST.nodes.*;
import IC.Analyzer.SymTabEntry;
import IC.lir.decl.*;
import IC.lir.instructions.*;


public class ClassInfo {
	
	private final String DVNAME_PREFIX = "_DV_";
	
	//** the class whose info we are collection */
	private ClassDecl classDecl;

	//** dispatch vector information */
	private String 		DVName;
	private List<Label> dispatchVector;
	
	//** dispatch vector declaration */
	private DispatchVector DVDecl;
	
	//** field offsets, start from 1, 0 is dispatch vector pointer  */
	private HashMap<String, Integer> fieldOffsets;
	private Integer fieldOffsetCounter = 1;
	
	//** method offsets, start from 0 */
	private HashMap<Pair<MethodDecl, ClassDecl>, Integer> methodOffsets;
	private Integer methodOffsetCounter = 0;	

	//** true iff the object has been updated since last calling generateDV */
	private boolean isChanged = true;
	
	/**
	 * 
	 * @param classDecl the class whose info we collect
	 */
	public ClassInfo(ClassDecl classDecl) {
		
		// init maps
		this.fieldOffsets = new HashMap<String, Integer>();
		this.methodOffsets = new HashMap<Pair<MethodDecl, ClassDecl>, Integer>();
		this.dispatchVector = null;
		
		// set class decl var
		this.classDecl = classDecl;
		
		// set DVName, with DVNAME_PREFIX prefix
		this.DVName = DVNAME_PREFIX+classDecl.getClassID(); 
				
		// set classInfo object in our class declaration
		classDecl.setClassInfo(this);
		
		
		String baseClassID = classDecl.getBaseClassID();
		SymTabEntry baseClassSymTab = classDecl.getSymTab().resolve(baseClassID);
		
		// we have a base class !
		// copy base class offsets 
		if(baseClassSymTab != null) {
			ClassDecl baseClassDecl = (ClassDecl)baseClassSymTab.getDeclNode();
			ClassInfo baseClassInfo = baseClassDecl.getClassInfo();
			
			// copy all values from base class
			
			this.fieldOffsets = (HashMap<String, Integer>) baseClassInfo.fieldOffsets
					.clone();
			
			this.methodOffsets = (HashMap<Pair<MethodDecl, ClassDecl>, Integer>) baseClassInfo.methodOffsets
					.clone();
			
			fieldOffsetCounter = baseClassInfo.getFieldOffsetCounter();
			methodOffsetCounter = baseClassInfo.getMethodOffsetCounter();
		}
		
	}
	
	/**
	 * 
	 * @return the class declaration
	 */
	public ClassDecl getClassDecl() {
		return classDecl;
	}

	/**
	 * 
	 * @param fieldID the fields ID
	 * @return field offset in the class
	 */
	public int getFieldOffset(String fieldID) {
		return fieldOffsets.get(fieldID);
	}
	
	/**
	 * 
	 * @return total field offset counter
	 */
	public int getFieldOffsetCounter() {
		return fieldOffsetCounter;
	}

	/**
	 * 
	 * @param methodID the method id
	 * @return method's offset in the class
	 * Pair<MethodDecl, ClassDecl>
	 */
	public int getMethodOffset(String methodID) {
		for(Entry<Pair<MethodDecl, ClassDecl>, Integer> e : methodOffsets.entrySet()) {
			if(e.getKey().getFirst().getMethodName().equals(methodID)) {
				return e.getValue();
			}
			
		}
		
		return -1;
	}
	
	/**
	 * 
	 * @return total method offset counter
	 */
	public int getMethodOffsetCounter() {
		return methodOffsetCounter;
	}
	
	/**
	 * Generates a dipatch vector object
	 */
	private void generateDV() {
		Set<Entry<Pair<MethodDecl, ClassDecl>, Integer>> eSet = methodOffsets.entrySet();
		Label[] dispatchVectorArr = new Label[methodOffsetCounter];
		
		for(Entry<Pair<MethodDecl, ClassDecl>, Integer> entry : eSet) {
			
			MethodDecl mDecl = entry.getKey().getFirst();
			ClassDecl cDecl = entry.getKey().getSecond();
			
			String labelName = "_"+cDecl.getClassID()+"_"+mDecl.getMethodName();
			
			Label lbl = new Label(labelName);
			
			dispatchVectorArr[entry.getValue()]= lbl;
			
		}
		this.dispatchVector = Arrays.asList(dispatchVectorArr);
		this.isChanged = false;

	}

	/**
	 * 
	 * @return a dispatch vector object for the current class
	 */
	public DispatchVector getDVDecl() {
		if(isChanged) {
			generateDV();
			this.DVDecl = new DispatchVector(DVName, dispatchVector);
		}
		
		return this.DVDecl;
	}
	
	/**
	 * 
	 * @return the dispatch vector name
	 */
	public String getDVName() {
		return DVName;
	}

	
	/**
	 * Add a field offset
	 * @param fDecl the field name
	 */
	public void addField(String fDecl) {
		this.fieldOffsets.put(fDecl, this.fieldOffsetCounter);
		this.fieldOffsetCounter++;
		this.isChanged = true;
	}

	
	/**
	 * Add the 'mDecl' method from the 'cDecl' class to the method offsets 
	 * @param mDecl a method decl
	 * @param cDecl a class decl
	 */
	public void addMethod(MethodDecl mDecl, ClassDecl cDecl) {
		this.methodOffsets.put(new Pair<MethodDecl, ClassDecl>(mDecl, cDecl),
				this.methodOffsetCounter);
		this.methodOffsetCounter++;
		this.isChanged = true;
	}

	/**
	 * Override a method which was defined in a base class
	 * @param baseDecl
	 * @param derivedDecl
	 * @param baseMethodDecl
	 * @param derivedMethodDecl
	 */
	public void overrideMethod(ClassDecl baseDecl, ClassDecl derivedDecl,
			MethodDecl baseMethodDecl, MethodDecl derivedMethodDecl) {
		
		Pair<MethodDecl, ClassDecl> oldKey = null;//new Pair<MethodDecl, ClassDecl>(baseMethodDecl, baseDecl);
		Pair<MethodDecl, ClassDecl> newKey = new Pair<MethodDecl, ClassDecl>(derivedMethodDecl, derivedDecl);

		Integer offset = null;
		
		for(Entry<Pair<MethodDecl, ClassDecl>, Integer> e : methodOffsets.entrySet()) {
			String methodName = e.getKey().getFirst().getMethodName();
			if(methodName.equals(baseMethodDecl.getMethodName())) {
				offset = e.getValue();
				oldKey = e.getKey();
				break;
			}
		}
		assert(offset != null);
		assert(oldKey != null);
		
		Integer res = methodOffsets.remove(oldKey);
		
		assert(res != null);
		
		methodOffsets.put(newKey, offset);
		
		this.isChanged = true;
		
	}

	/**
	 * 
	 * @return the size of the class in bytes, where each field is 4 bytes 
	 * including the size of the dispatch vector pointer
	 */
	public int getSize() {
		return (fieldOffsets.size()+1)*4;
	}

	/**
	 * Generate a string repersentation of the ClassInfo
	 * @return a string repersentation of the ClassInfo
	 */
	public String toString() {
		String s = "";
		
		s += "Class size in bytes:" + getSize() + "\n"; 
		s += "field offsets: ";

		for (String key : fieldOffsets.keySet()) {
			Integer val = fieldOffsets.get(key);
			s += key + ":" + val + ", ";
		}
		s += "\n";
			
		DispatchVector dv = (DispatchVector)getDVDecl();

		s += dv.getName()+":"+dv.getLabels();

		return s;
	}
}
